
/*
 * Copyright (c) 2021. Dylan Cai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:Suppress("unused")

//package com.dylanc.longan.design

package com.gitee.wsl.android.ui.ext

import android.view.ViewGroup
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.*
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.LinearSmoothScroller.SNAP_TO_END
import androidx.recyclerview.widget.LinearSmoothScroller.SNAP_TO_START
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor

fun RecyclerView.setEmptyView(owner: LifecycleOwner, emptyView: View) =
    observeDataEmpty(owner) { emptyView.isVisible = it }

fun RecyclerView.observeDataEmpty(owner: LifecycleOwner, block: (Boolean) -> Unit) =
    owner.lifecycle.addObserver(object : DefaultLifecycleObserver {
        private var observer: RecyclerView.AdapterDataObserver? = null

        override fun onCreate(owner: LifecycleOwner) {
            if (observer == null) {
                val adapter = checkNotNull(adapter) {
                    "RecyclerView needs to set up the adapter before setting up an empty view."
                }
                observer = AdapterDataEmptyObserver(adapter, block)
                adapter.registerAdapterDataObserver(observer!!)
            }
        }

        override fun onDestroy(owner: LifecycleOwner) {
            observer?.let {
                adapter?.unregisterAdapterDataObserver(it)
                observer = null
            }
        }
    })


fun RecyclerView.smoothScrollToStartPosition(position: Int) =
    smoothScrollToPosition(position, SNAP_TO_START)

fun RecyclerView.smoothScrollToEndPosition(position: Int) =
    smoothScrollToPosition(position, SNAP_TO_END)

fun RecyclerView.smoothScrollToPosition(position: Int, snapPreference: Int) =
    layoutManager?.let {
        val smoothScroller = LinearSmoothScroller(context, snapPreference)
        smoothScroller.targetPosition = position
        it.startSmoothScroll(smoothScroller)
    }

fun RecyclerView.smoothScrollToPositionCenter(
    snapHelper: SnapHelper,
    duration: Int,
    position: Int
) {
    if (position < 0) {
        return
    }
    val layoutManager = this.layoutManager as? LinearLayoutManager ?: return
    val view = layoutManager.findViewByPosition(position)

    if (view == null) {
        val linearSmoothScroller = CenterLinearSmoothScroller(this.context)
        linearSmoothScroller.targetPosition = position
        layoutManager.startSmoothScroll(linearSmoothScroller)
        return
    }
    val snapDistance =
        snapHelper.calculateDistanceToFinalSnap(layoutManager, view)
            ?: return

    this.smoothScrollBy(snapDistance[0], snapDistance[1], null, duration)

}

class CenterLinearSmoothScroller(context: Context) : LinearSmoothScroller(context) {
    override fun calculateDtToFit(
        viewStart: Int,
        viewEnd: Int,
        boxStart: Int,
        boxEnd: Int,
        snapPreference: Int
    ): Int {
        val childCenter = viewStart + ((viewEnd - viewStart) / 2)
        val containerCenter = boxStart + ((boxEnd - boxStart) / 2)
        return containerCenter - childCenter
    }
}

fun LinearSmoothScroller(context: Context, snapPreference: Int) =
    object : LinearSmoothScroller(context) {
        override fun getVerticalSnapPreference() = snapPreference
        override fun getHorizontalSnapPreference() = snapPreference
    }

fun RecyclerView.addItemPadding(padding: Int) = addItemPadding(padding, padding, padding, padding)

fun RecyclerView.addItemPadding(top: Int, bottom: Int, left: Int = 0, right: Int = 0) =
    addItemDecoration(object : RecyclerView.ItemDecoration() {
        override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
            super.getItemOffsets(outRect, view, parent, state)
            outRect.bottom = bottom
            outRect.top = top
            outRect.left = left
            outRect.right = right
        }
    })

class AdapterDataEmptyObserver(
    private val adapter: RecyclerView.Adapter<*>,
    private val checkEmpty: (Boolean) -> Unit
) : RecyclerView.AdapterDataObserver() {

    override fun onChanged() = checkEmpty(isDataEmpty)

    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) = checkEmpty(isDataEmpty)

    override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) = checkEmpty(isDataEmpty)

    private val isDataEmpty get() = adapter.itemCount == 0
}


/**
 * A wrapper for [OnScrollListener] to tell which ItemView is showing or gone according to [percent]
 * @param viewGroups the parent ViewGroups which new view to be added in (like Fragment's Container View)
 * @param percent the percentage of ItemView's visibility, range from 0 to 1, for example, 0.5 means the half of ItemView is visible to user.
 * @param block a lambda to tell which ItemView is visible to user with the Adapter Index
 */
fun RecyclerView.onItemVisibilityChange(
    percent: Float = 0.5f,
    viewGroups: List<ViewGroup>? = null,
    block: (itemView: View, adapterIndex: Int, isVisible: Boolean) -> Unit
) {
    val visibleRect = Rect() // reuse rect object rather than recreate it everytime for a better performance
    val visibleAdapterIndexs = mutableSetOf<Int>()
    val checkVisibility = {
        // iterate all children of RecyclerView to check whether it is visible
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val adapterIndex = getChildAdapterPosition(child)
            if (adapterIndex == RecyclerView.NO_POSITION) continue
            val isChildVisible = child.getLocalVisibleRect(visibleRect)
            val visibleArea = visibleRect.let { it.height() * it.width() }
            val realArea = child.width * child.height
            if (visiblePercent(visibleRect) > percent && isChildVisible && visibleArea >= realArea * percent) {
                if (visibleAdapterIndexs.add(adapterIndex)) {
                    block(child, adapterIndex, true)
                }
            } else {
                if (adapterIndex in visibleAdapterIndexs) {
                    block(child, adapterIndex, false)
                    visibleAdapterIndexs.remove(adapterIndex)
                }
            }
        }
    }
    val scrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            checkVisibility()
        }
    }
    addOnScrollListener(scrollListener)
    onVisibilityChange(viewGroups, false) { view, isVisible ->
        if (isVisible) {
            checkVisibility()
        } else {
            for (i in 0 until childCount) {
                val child = getChildAt(i)
                val adapterIndex = getChildAdapterPosition(child)
                if (adapterIndex in visibleAdapterIndexs) {
                    block(child, adapterIndex, false)
                    visibleAdapterIndexs.remove(adapterIndex)
                }
            }
        }
    }
    addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View) {
        }

        override fun onViewDetachedFromWindow(v: View) {
            if (v == null || v !is RecyclerView) return
            v.removeOnScrollListener(scrollListener)
            removeOnAttachStateChangeListener(this)
        }
    })
}
/**
 * add listener to RecyclerView which listens it's items in and out event
 * @param inOrOut 1 means item enter RecyclerView, 0 means item leave RecyclerView, 2 means item is fully visible in RecyclerView
 */
fun RecyclerView.addItemInOutListener(listener: ((childView: View?, adapterIndex: Int, inOrOut: Int) -> Unit)?) {
    data class Item(var index: Int, var view: View?) {
        infix fun after(item: Item) = this.index > item.index
        infix fun before(item: Item) = this.index < item.index

        val isInvalid = index < 0
    }
    addOnScrollListener(object : RecyclerView.OnScrollListener() {
        private var preTopItem = Item(-1, null)
        private var preBottomItem = Item(-1, null)
        private var curTopItem = Item(0, null)
        private var curBottomItem = Item(Int.MAX_VALUE, null)

        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            recyclerView.findChildViewUnder(0f, 0f)?.let { topChildView ->
                val index = recyclerView.getChildAdapterPosition(topChildView)
                curTopItem = curTopItem.copy(index, topChildView)
                if (preTopItem.isInvalid) preTopItem = curTopItem
                if (preTopItem before curTopItem) listener?.invoke(preTopItem.view, preTopItem.index, 0)
                else if (preTopItem after curTopItem) {
                    listener?.invoke(curTopItem.view, curTopItem.index, 1)
                    listener?.invoke(preTopItem.view, preTopItem.index, 2)
                }
                preTopItem = preTopItem.copy(curTopItem.index, curTopItem.view)
            }

            recyclerView.findChildViewUnder(recyclerView.width.toFloat(), recyclerView.height.toFloat())?.let { bottomChildView ->
                val index = recyclerView.getChildAdapterPosition(bottomChildView)
                curBottomItem = curBottomItem.copy(index, bottomChildView)
                if (preBottomItem.isInvalid) preBottomItem = curBottomItem
                if (preBottomItem before curBottomItem) {
                    listener?.invoke(curBottomItem.view, curBottomItem.index, 1)
                    listener?.invoke(preBottomItem.view, preBottomItem.index, 2)
                } else if (preBottomItem after curBottomItem) listener?.invoke(preBottomItem.view, preBottomItem.index, 0)
                preBottomItem = preBottomItem.copy(curBottomItem.index, curBottomItem.view)
            }
        }
    })
}


/**
 * add listener to [RecyclerView] which listens whether it's scrolling is touch the top(return 1) or bottom(return 2)
 */
fun RecyclerView.addTopBottomListener(listener: ((direction: Int) -> Unit)?) {
    addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            if (dy == 0) return
            if (!recyclerView.canScrollVertically(1)) {
                listener?.invoke(2)
            } else if (!recyclerView.canScrollVertically(-1)) {
                listener?.invoke(1)
            }
        }
    })
}

fun RecyclerView.calculateAnimationDelay(
    position: Int,
    delayOffset: Int
): Int {

    var delay = delayOffset

    var firstVisiblePosition = findFirstCompletelyVisibleItemPosition()
    var lastVisiblePosition = findLastCompletelyVisibleItemPosition()

    if (firstVisiblePosition < 0 && position >= 0) {
        firstVisiblePosition = position - 1
    }

    if (position - 1 > lastVisiblePosition) {
        lastVisiblePosition = position - 1
    }

    val visibleItems = lastVisiblePosition - firstVisiblePosition
    val numberOfAnimatedItems = position - 1

    if (visibleItems < numberOfAnimatedItems) {
        val numColumns = getSpanCount()
        val n = position % numColumns
        delay += delayOffset * n
    } else {
        delay = position * delayOffset / 2
    }

    return delay
}

fun RecyclerView.getSpanCount(): Int {
    return when (val manager = layoutManager) {
        is GridLayoutManager -> {
            manager.spanCount
        }
        is StaggeredGridLayoutManager -> {
            manager.spanCount
        }
        else -> {
            return 1
        }
    }
}

fun RecyclerView.findFirstCompletelyVisibleItemPosition(): Int {
    when (val manager = layoutManager) {
        is StaggeredGridLayoutManager -> {
            var position = manager.findFirstCompletelyVisibleItemPositions(null)[0]
            for (i in 1 until getSpanCount()) {
                val nextPosition: Int = manager.findFirstCompletelyVisibleItemPositions(null)[i]
                if (nextPosition < position) {
                    position = nextPosition
                }
            }
            return position
        }
        is LinearLayoutManager -> {
            return manager.findFirstCompletelyVisibleItemPosition()
        }
        else -> {
            throw IllegalArgumentException("Not supported this :${manager?.javaClass?.name}")
        }
    }
}

fun RecyclerView.findLastCompletelyVisibleItemPosition(): Int {
    when (val manager = layoutManager) {
        is StaggeredGridLayoutManager -> {
            var position = manager.findLastCompletelyVisibleItemPositions(null)[0]
            for (i in 1 until getSpanCount()) {
                val nextPosition: Int = manager.findLastCompletelyVisibleItemPositions(null)[i]
                if (nextPosition > position) {
                    position = nextPosition
                }
            }
            return position
        }
        is LinearLayoutManager -> {
            return manager.findLastCompletelyVisibleItemPosition()
        }
        else -> {
            throw IllegalArgumentException("Not supported this :${manager?.javaClass?.name}")
        }
    }
}

val RecyclerView.firstVisibleItemPosition: Int
    get() = with(layoutManager) {
        when (this) {
            is GridLayoutManager -> findFirstVisibleItemPosition()
            is LinearLayoutManager -> findFirstVisibleItemPosition()
            is StaggeredGridLayoutManager -> findFirstVisibleItemPositions(null)[0]
            else -> -1
        }
    }

val RecyclerView.lastVisibleItemPosition: Int
    get() = with(layoutManager) {
        when (this) {
            is GridLayoutManager -> findLastVisibleItemPosition()
            is LinearLayoutManager -> findLastVisibleItemPosition()
            is StaggeredGridLayoutManager -> findLastVisibleItemPositions(null).last()
            else -> -1
        }
    }

fun RecyclerView.LayoutManager.orientation() = when (this) {
    is GridLayoutManager -> this.orientation
    is LinearLayoutManager -> this.orientation
    is StaggeredGridLayoutManager -> this.orientation
    else -> RecyclerView.VERTICAL
}

fun <T> DiffUtil.ItemCallback<T>.asConfig(): AsyncDifferConfig<T> {
    return AsyncDifferConfig.Builder(this)
        .setBackgroundThreadExecutor(Dispatchers.IO.asExecutor())
        .build()
}


/**
 * 配置某些位置独占一行
 * @param checkFullSpan 判断是否只独占一行
 */
fun <G : GridLayoutManager> G.configFullSpan(checkFullSpan: (position: Int) -> Boolean): G {
    val oldSpanSizeLookup = spanSizeLookup
    val fullSpanCount = spanCount
    spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
        override fun getSpanSize(position: Int): Int {
            return if (checkFullSpan(position)) fullSpanCount else oldSpanSizeLookup.getSpanSize(
                position
            )
        }
    }
    return this
}


/**
 * Created by ve3344@qq.com.
 */

/**
 * 连接2个 adapter
 */
operator fun RecyclerView.Adapter<*>.plus(o: RecyclerView.Adapter<*>): ConcatAdapter {
    if (this is ConcatAdapter) {
        this.addAdapter(o)
        return this
    }
    if (o is ConcatAdapter) {
        o.addAdapter(0, this)
        return o
    }
    return ConcatAdapter(this, o)
}

/**
 * 获取该position 所在的Adapter
 * @param position 位置
 * @return Adapter 所在的Adapter ，null 没有对应的Adapter
 */
fun ConcatAdapter.getAdapterByItemPosition(position: Int): RecyclerView.Adapter<out RecyclerView.ViewHolder>? {
    var pos = position
    val adapters = adapters
    for (adapter in adapters) {
        when {
            pos >= adapter.itemCount -> {
                pos -= adapter.itemCount
            }
            pos < 0 -> return null
            else -> return adapter
        }
    }
    return null
}

/**
 * 把全局位置转换为子Adapter的相对位置
 * @param adapter
 * @param globalPosition
 */
fun ConcatAdapter.findLocalPositionAt(
    adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
    globalPosition: Int
): Int {
    val itemsBefore = countItemsBefore(adapter)
    if (itemsBefore == RecyclerView.NO_POSITION) {
        return RecyclerView.NO_POSITION
    }
    val localPosition: Int = globalPosition - itemsBefore
    val itemCount: Int = adapter.itemCount

    if (localPosition < 0 || localPosition >= itemCount) {
        return RecyclerView.NO_POSITION
    }

    return localPosition
}

fun RecyclerView.Adapter<*>.countItemsBefore(adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>): Int {
    if (this == adapter) {
        return 0
    }
    check(this is ConcatAdapter) { "adapter $adapter is not in $this" }
    var count = 0
    val adapters = adapters

    for (item in adapters) {
        if (item !== adapter) {
            count += item.itemCount
        } else {
            return count
        }
    }
    return RecyclerView.NO_POSITION
}

/**
 * 递归遍历所有Adapter包括this
 *
 */
fun RecyclerView.Adapter<*>.allAdapter(): Sequence<RecyclerView.Adapter<*>> {
    if (this !is ConcatAdapter) {
        return sequenceOf(this)
    }
    return adapters.asSequence().flatMap { it.allAdapter() }

}